Skip to content

release: v0.8.0-alpha.1 — Lock methods, Exit, Mutex/Once/WaitGroup, Signal, SyncMap + atomics, AX-10#17

Open
Snider wants to merge 3 commits intomainfrom
release/v0.8.0-alpha.1
Open

release: v0.8.0-alpha.1 — Lock methods, Exit, Mutex/Once/WaitGroup, Signal, SyncMap + atomics, AX-10#17
Snider wants to merge 3 commits intomainfrom
release/v0.8.0-alpha.1

Conversation

@Snider
Copy link
Copy Markdown
Contributor

@Snider Snider commented Apr 25, 2026

Summary

Consolidates the v0.8.0-alpha.1 primitives sprint for core/go. RFC-cited new primitives unblock banned-import purges across the workspace.

Headline changes

  • AX-10 scaffoldtests/cli/core/Taskfile.yaml + driver coverage for the core CLI surface
  • §13 Lock methodsLock, Unlock, RLock, RUnlock, TryLock on *Lock
  • §13A Exit primitivec.Exit(code), c.ExitWith(code, message), c.ExitNow(code), package-level core.Exit
  • §14A Mutex / RWMutex / Once / WaitGroup — value-type wrappers around sync.*, embed-friendly, zero-value-usable
  • §14B SyncMap + typed atomics — concurrent map plus AtomicBool, AtomicInt32/64, AtomicUint32/64, AtomicPointer[T]
  • §15A Signal primitivec.Signal() accessor for OS signal handling via the action-subscription contract

Coverage

Test triad per AX-10 (Good, Bad, Ugly) for every new primitive, including:

  • 1000-op concurrent Add for AtomicInt32/Int64/Uint64
  • 100-goroutine CAS-claim race for AtomicBool (exactly one winner)
  • 100-goroutine concurrent Store on SyncMap

All pass under -race.

Out of scope (deferred per RFC §14B)

  • sync.Pool — 3 workspace hits, deferred until consumer count exceeds 10
  • sync.Cond — 0 hits, file amendment if needed
  • sync/atomic And/Or — 0 hits, add when needed (AX-4)

Refs

  • RFC.primitives-lifecycle.md §13, §13A, §14A, §14B, §15A
  • RFC-CORE-008-AGENT-EXPERIENCE.md (AX-1, AX-4, AX-6, AX-10)

Test plan

  • go test ./... passes under -race
  • tests/cli/core Taskfile drivers exercise the CLI surface
  • CodeRabbit review

Co-authored-by: Athena athena@lthn.ai
Co-authored-by: Hephaestus hephaestus@lthn.ai
Co-authored-by: Cladius Maximus cladius@lthn.ai

Summary by CodeRabbit

  • New Features

    • Added atomic primitives for thread-safe operations on booleans, integers, pointers and a generic pointer type
    • Implemented graceful shutdown mechanism with configurable timeout support and immediate-exit option
    • Enhanced locking API with convenience methods and non-blocking acquisition
    • Added OS signal handling capability
    • Introduced wrapped synchronisation primitives and a thread-safe concurrent map
  • Tests

    • Added comprehensive examples and tests validating the new primitives and behaviours
  • Improvements

    • Refined exit handling to support graceful service shutdown chains

…ignal, SyncMap + atomics, AX-10

Consolidates the v0.8.0-alpha.1 primitives sprint. Headline changes
correspond to RFC.primitives-lifecycle.md sections §13–§15.

* AX-10 scaffold: tests/cli/core/Taskfile.yaml + driver coverage for
  the core CLI surface.

* §13 Lock methods on *Lock — Lock, Unlock, RLock, RUnlock, TryLock.
  Existing *Lock zero-value type gets the standard sync.RWMutex method
  surface as a Core primitive, eliminating direct sync.RWMutex usage in
  consumer code.

* §13A Exit primitive — c.Exit(code), c.ExitWith(code, message),
  c.ExitNow(code), and package-level core.Exit shortcut. Replaces
  os.Exit at every consumer site, with structured-shutdown hooks the
  service can intercept.

* §14A Mutex / RWMutex / Once / WaitGroup — value-type wrappers around
  the sync.* primitives. Embed-friendly, zero-value-usable, unexported
  inner field prevents bypassing the wrapper to reintroduce banned
  imports.

* §14B SyncMap + typed atomics — concurrent map and typed atomic
  primitives mirroring sync.Map and sync/atomic 1:1:
    - SyncMap (Load, Store, LoadOrStore, LoadAndDelete, Delete, Swap,
      CompareAndSwap, CompareAndDelete, Range, Clear)
    - AtomicBool, AtomicInt32, AtomicInt64, AtomicUint32, AtomicUint64,
      AtomicPointer[T]
  Audit found 12 sync.Map call-sites and 20 sync/atomic call-sites
  across the workspace; these primitives unblock the corresponding
  banned-import purges in api, go-i18n, go-cgo, go-mlx, go-pool, gui,
  agent, go-infra, go-process, go-store and others.

* §15A Signal primitive — c.Signal() accessor for OS signal handling
  via the action-subscription contract:
    - signal.received  service -> consumers   {name, value}
    - signal.start     consumer -> service    {signals: []string}
    - signal.stop      consumer -> service    {}
  Permission-by-registration mirrors the Process pattern; if go-process
  is not registered no signal actions fire.

Test triad per AX-10 (Good, Bad, Ugly) for every new primitive,
including 1000-op concurrent Add (Int32/Int64/Uint64), 100-goroutine
CAS-claim race for AtomicBool, and 100-goroutine concurrent Store on
SyncMap. All pass under -race.

Out of scope (deferred per RFC §14B):
* sync.Pool — 3 hits, deferred until consumer count exceeds 10
* sync.Cond — 0 hits, file amendment if needed
* sync/atomic And/Or — 0 hits, add when needed (AX-4)

Refs: RFC.primitives-lifecycle.md §13, §13A, §14A, §14B, §15A
      RFC-CORE-008-AGENT-EXPERIENCE.md (AX-1, AX-4, AX-6, AX-10)

Co-authored-by: Athena <athena@lthn.ai>
Co-authored-by: Hephaestus <hephaestus@lthn.ai>
Co-authored-by: Cladius Maximus <cladius@lthn.ai>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Warning

Rate limit exceeded

@Snider has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 11 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 5 minutes and 11 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f543cab0-896a-4cc9-9014-0280147b4cf5

📥 Commits

Reviewing files that changed from the base of the PR and between a848f7c and c067098.

📒 Files selected for processing (2)
  • exit.go
  • exit_test.go
📝 Walkthrough

Walkthrough

Adds new race-free concurrency primitives (typed atomics, SyncMap, sync wrappers), enhanced Lock API, signal handling, and a centralized exit/shutdown mechanism with configurable timeouts; includes tests and examples for all additions and a Taskfile for CLI workflows.

Changes

Cohort / File(s) Summary
Atomic primitives
core/atomic.go, core/atomic_test.go, core/atomic_example_test.go
Introduces typed atomic wrappers (AtomicBool, AtomicInt32, AtomicInt64, AtomicUint32, AtomicUint64, AtomicPointer[T]) exposing Load/Store/Add/Swap/CompareAndSwap variants; adds tests and runnable examples.
Exit / shutdown
core/exit.go, core/exit_test.go, core/exit_example_test.go, core/core.go
Centralises process termination via ExitOptions, (*Core).Exit/ExitWith/ExitNow and package Exit; ExitWith runs ServiceShutdown with configurable timeout and uses an overridable osExit hook; Core.Run updated to call ExitNow(1) on RunE failure.
Lock API
core/lock.go, core/lock_test.go, core/lock_example_test.go
Deprecates direct Mutex field access and adds method-based API on *Lock: Lock/Unlock, RLock/RUnlock, and TryLock() Result; tests and examples updated/added.
Signal primitive
core/signal.go, core/signal_test.go, core/signal_example_test.go
Adds Signal accessible via Core.Signal() with Stop() and Exists() methods and defines signal.* action contract for OS-signal broadcasting; includes tests and examples.
Sync wrappers
core/sync.go, core/sync_test.go, core/sync_example_test.go
Adds core-scoped wrappers for sync primitives: Mutex, RWMutex, Once (with Reset), and WaitGroup, including non-blocking Try variants returning Result{OK:...}; tests and examples added.
SyncMap wrapper
core/syncmap.go, core/syncmap_test.go, core/syncmap_example_test.go
Introduces SyncMap wrapper over sync.Map with Load/Store/LoadOrStore/LoadAndDelete/Delete/Swap/CompareAndSwap/CompareAndDelete/Range/Clear methods and corresponding tests/examples.
CLI Taskfile
tests/cli/core/Taskfile.yaml
Adds Taskfile with default, build, vet, and test tasks configured to run from repo root with GOWORK=off.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Core
  participant ServiceShutdown as "ServiceShutdown (shutdown goroutine)"
  participant Timer

  Caller->>Core: Call ExitWith(opts{Code, Timeout})
  Core->>ServiceShutdown: start shutdown (goroutine)
  alt Timeout > 0
    Core->>Timer: start timeout
    par wait
      ServiceShutdown-->>Core: shutdown completes
      Timer-->>Core: timeout fires
    end
    alt shutdown completed first
      Core->>Caller: return (or proceed)
      Core->>os: osExit(opts.Code)  %% conceptual termination
    else timeout fired first
      Core->>Core: log warning
      Core->>os: osExit(opts.Code)
    end
  else Timeout == 0
    ServiceShutdown-->>Core: shutdown completes (block until done)
    Core->>os: osExit(opts.Code)
  end
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the major features in this release: Lock methods, Exit primitive, concurrency wrappers (Mutex/Once/WaitGroup), Signal, SyncMap, and atomics. It is specific, concise, and clearly reflects the primary changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
exit_example_test.go (1)

11-42: Examples document shape but do not execute the Exit* APIs.

Line 13, Line 22, Line 32, and Line 40 keep the critical calls commented, so these examples cannot validate wiring or runtime behaviour. Consider moving these examples to an internal test package setup that can safely override the exit hook and invoke the real methods.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@exit_example_test.go` around lines 11 - 42, The examples (ExampleCore_Exit,
ExampleCore_ExitWith, ExampleCore_ExitNow, and ExampleExit) currently avoid
calling the real Exit APIs so they don't validate behavior; move these into an
internal test package where you can safely override the process-exit hook and
then call the real methods (Core.Exit, Core.ExitWith/ExitOptions, Core.ExitNow,
and package-level Exit) to assert runtime wiring and shutdown behavior, ensuring
you replace the commented calls with actual calls under the test-only exit stub
and verify outputs/timeouts accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core.go`:
- Around line 164-167: The code logs the error then calls c.Exit(1), which
triggers shutdown hooks a second time because c.RunE() already deferred
ServiceShutdown; fix by removing the c.Exit(1) call and only log the error
(Error(err.Error())) and then let the process exit naturally or call os.Exit(1)
directly if a hard exit is required—do not invoke c.Exit(1) after c.RunE(), and
ensure ServiceShutdown is only invoked by the defer inside RunE().

In `@exit.go`:
- Around line 65-69: The code silently ignores negative ExitOptions.Timeout
values; add an explicit check for opts.Timeout < 0 and handle it as an invalid
argument instead of falling through to "wait forever": before the existing if
opts.Timeout > 0 block, validate opts.Timeout and return an error (or propagate
an error) indicating a negative timeout is invalid, referencing
ExitOptions.Timeout and the surrounding context/WithTimeout logic so callers
cannot pass negative durations; do not rely on the > 0 branch to catch this.

In `@lock_test.go`:
- Around line 89-96: The test TestLock_RLockRUnlock_Bad is exercising the
write-lock path instead of the intended read-lock bad path; change the setup to
acquire a read lock on the lock returned by New().Lock("write-held") by calling
l.RLock() and defer l.RUnlock() (instead of l.Lock()/defer l.Unlock()), then
call l.TryLock() and assert r.OK is false to verify TryLock fails while a reader
holds the lock; keep the same assertion message and use the existing New(),
Lock(), RLock(), RUnlock(), and TryLock() symbols to locate and update the code.

---

Nitpick comments:
In `@exit_example_test.go`:
- Around line 11-42: The examples (ExampleCore_Exit, ExampleCore_ExitWith,
ExampleCore_ExitNow, and ExampleExit) currently avoid calling the real Exit APIs
so they don't validate behavior; move these into an internal test package where
you can safely override the process-exit hook and then call the real methods
(Core.Exit, Core.ExitWith/ExitOptions, Core.ExitNow, and package-level Exit) to
assert runtime wiring and shutdown behavior, ensuring you replace the commented
calls with actual calls under the test-only exit stub and verify
outputs/timeouts accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f1ac6bfb-493e-43d1-a81b-56612f29bfa9

📥 Commits

Reviewing files that changed from the base of the PR and between fb1b790 and 9aba780.

📒 Files selected for processing (20)
  • atomic.go
  • atomic_example_test.go
  • atomic_test.go
  • core.go
  • exit.go
  • exit_example_test.go
  • exit_test.go
  • lock.go
  • lock_example_test.go
  • lock_test.go
  • signal.go
  • signal_example_test.go
  • signal_test.go
  • sync.go
  • sync_example_test.go
  • sync_test.go
  • syncmap.go
  • syncmap_example_test.go
  • syncmap_test.go
  • tests/cli/core/Taskfile.yaml

Comment thread core.go
Comment thread exit.go Outdated
Comment on lines +65 to +69
if opts.Timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
defer cancel()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle negative ExitOptions.Timeout explicitly.

Line 65 only checks > 0, so negative values silently become “wait forever”, which can hang termination on misconfiguration.

Proposed fix
 func (c *Core) ExitWith(opts ExitOptions) {
 	ctx := context.Background()
-	if opts.Timeout > 0 {
+	switch {
+	case opts.Timeout < 0:
+		Warn("negative exit timeout, forcing immediate termination",
+			"timeout", opts.Timeout, "code", opts.Code)
+		osExit(opts.Code)
+		return
+	case opts.Timeout > 0:
 		var cancel context.CancelFunc
 		ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
 		defer cancel()
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@exit.go` around lines 65 - 69, The code silently ignores negative
ExitOptions.Timeout values; add an explicit check for opts.Timeout < 0 and
handle it as an invalid argument instead of falling through to "wait forever":
before the existing if opts.Timeout > 0 block, validate opts.Timeout and return
an error (or propagate an error) indicating a negative timeout is invalid,
referencing ExitOptions.Timeout and the surrounding context/WithTimeout logic so
callers cannot pass negative durations; do not rely on the > 0 branch to catch
this.

Comment thread lock_test.go
Comment on lines +89 to +96
func TestLock_RLockRUnlock_Bad(t *testing.T) {
c := New()
l := c.Lock("write-held")
l.Lock()
defer l.Unlock()
r := l.TryLock()
assert.False(t, r.OK, "TryLock when write-held must fail (readers also blocked)")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

RLockRUnlock_Bad currently doesn't exercise the read-lock bad path.

This test re-validates the write-held case (l.Lock()), which is already covered elsewhere. For the intended scenario, hold RLock() and assert TryLock() fails.

🧪 Proposed test adjustment
 func TestLock_RLockRUnlock_Bad(t *testing.T) {
 	c := New()
 	l := c.Lock("write-held")
-	l.Lock()
-	defer l.Unlock()
+	l.RLock()
+	defer l.RUnlock()
 	r := l.TryLock()
-	assert.False(t, r.OK, "TryLock when write-held must fail (readers also blocked)")
+	assert.False(t, r.OK, "TryLock when read-held must fail (writer blocked by reader)")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lock_test.go` around lines 89 - 96, The test TestLock_RLockRUnlock_Bad is
exercising the write-lock path instead of the intended read-lock bad path;
change the setup to acquire a read lock on the lock returned by
New().Lock("write-held") by calling l.RLock() and defer l.RUnlock() (instead of
l.Lock()/defer l.Unlock()), then call l.TryLock() and assert r.OK is false to
verify TryLock fails while a reader holds the lock; keep the same assertion
message and use the existing New(), Lock(), RLock(), RUnlock(), and TryLock()
symbols to locate and update the code.

… close Mantis #926

Run() called c.Exit(1) after RunE returned an error, but RunE's defer chain
already ran ServiceShutdown. c.Exit(1) → c.ExitWith(...) → c.ServiceShutdown
fired the chain a second time, double-firing all OnStop hooks. Non-idempotent
hooks (close-then-close on sql.DB, release-twice, double-flush) cause
real incident on any RunE-error path.

Fix per CodeRabbit + Cerberus DREAD review:
- c.Exit(1) → c.ExitNow(1) (skip shutdown, RunE's defer already ran it)
- Comment at the call site documenting why ExitNow is correct here

Test: TestRun_FailurePath_ShutdownFiresOnce_Bad asserts shutdownCount=1
on the failure path (was 2 before the fix).

Filer: CodeRabbit (PR #17 dAppCore/core)
DREAD review: Cerberus (Mantis #926 verdict REACHABLE-high)

Co-Authored-By: CodeRabbit <noreply@coderabbit.ai>
Co-Authored-By: Cerberus <cerberus@lthn.ai>
Co-Authored-By: Athena <athena@lthn.ai>
Co-Authored-By: Virgil <virgil@lethean.io>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@exit_test.go`:
- Line 121: Replace the direct errors.New usage with the repository-standard
structured error helper: change panic(errors.New("boom")) to
panic(core.E("boom")) (or wrap as appropriate) in the anonymous function; ensure
the core package is imported where exit_test.go uses panic(...) and update the
other call site at the second occurrence (around line 152) similarly to use
core.E().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 70179734-8061-472f-90c1-bc2647ecca7f

📥 Commits

Reviewing files that changed from the base of the PR and between 9aba780 and a848f7c.

📒 Files selected for processing (3)
  • core.go
  • exit.go
  • exit_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • core.go
  • exit.go

Comment thread exit_test.go
c.ExitNow(2)
}
}()
func() { panic(errors.New("boom")) }()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace errors.New with the repository-standard core.E() path.

These two call sites currently bypass the project’s structured error helper.

As per coding guidelines "Use core.E() for structured error handling; never use fmt.Errorf, errors.New, os/exec, or unsafe.Pointer on Core types".

Also applies to: 152-152

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@exit_test.go` at line 121, Replace the direct errors.New usage with the
repository-standard structured error helper: change panic(errors.New("boom")) to
panic(core.E("boom")) (or wrap as appropriate) in the anonymous function; ensure
the core package is imported where exit_test.go uses panic(...) and update the
other call site at the second occurrence (around line 152) similarly to use
core.E().

…fallback — close Mantis #927

ExitWith's `if opts.Timeout > 0` check fell through to context.Background()
for negative durations, silently producing wait-forever behaviour. A
misconfigured `timeout: -1` (intending "no timeout") would succeed AND
hang on shutdown.

Fix per Cerberus DREAD review:
- switch statement explicitly handles three cases: negative (invalid →
  warn + 30s default), positive (use as bound), zero (preserved
  wait-forever legacy semantics per RFC §15B)
- Warn() logs the invalid value via the standard core logger
- ExitOptions.Timeout docstring updated: "Negative is invalid — ExitWith
  warns and falls back to a 30s default rather than silently waiting forever."

Tests:
- TestExit_ExitWithNegativeTimeout_Bad — assert elapsed < 5s on
  Timeout=-1 (default fallback bounds shutdown)
- TestExit_ExitWithZeroTimeout_Good — assert documented zero-means-wait-
  forever-but-returns-when-ready semantics still works

Filer: CodeRabbit (PR #17 dAppCore/core)
DREAD review: Cerberus (Mantis #927 verdict REACHABLE-medium)

Co-Authored-By: CodeRabbit <noreply@coderabbit.ai>
Co-Authored-By: Cerberus <cerberus@lthn.ai>
Co-Authored-By: Athena <athena@lthn.ai>
Co-Authored-By: Virgil <virgil@lethean.io>
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant